package com.github.data_manage.controller

import cn.hutool.core.collection.ConcurrentHashSet
import cn.idev.excel.EasyExcel
import cn.idev.excel.support.ExcelTypeEnum
import com.github.data_manage.common.CommonPage
import com.github.data_manage.common.CommonResult
import com.github.data_manage.common.ResultCode
import com.github.data_manage.config.SystemException
import com.github.data_manage.controller.websocket.FundImportStatusHandler
import com.github.data_manage.dao.*
import com.github.data_manage.domain.dto.QuarterAnnualizedResults
import com.github.data_manage.domain.dto.YearAnnualizedResults
import com.github.data_manage.enum.RelationType
import com.github.data_manage.domain.po.*
import com.github.data_manage.domain.vo.*
import com.github.data_manage.service.ContrastService
import com.github.data_manage.service.FundDeepResearchService
import com.github.data_manage.service.FundService
import com.github.data_manage.util.*
import com.github.data_manage.util.DataUtils.ytd
import com.github.data_manage.util.DateTimeUtils.Companion.atEndOfMonth
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.servlet.http.HttpServletResponse
import jakarta.transaction.Transactional
import kotlinx.coroutines.*
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.BeanUtils
import org.springframework.core.io.ClassPathResource
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.util.StringUtils
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import java.math.BigDecimal
import java.math.RoundingMode
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.UUID
import java.util.concurrent.atomic.LongAdder
import kotlin.time.measureTime

/**
 *@author CHN
 *@since 2024-08-10 14:48
 */
@RestController
@Tag(name = "FundController", description = "基金管理")
@RequestMapping("/funds")
open class FundController(
    private val fundDao: FundDao,
    private val fundTrancheDao: FundTrancheDao,
    private val fundInternalScoreDao: FundInternalScoreDao,
    private val fundInternalScoreHistoryDao: FundInternalScoreHistoryDao,
    private val fundInvestmentsDao: FundInvestmentsDao,
    private val fundPersonDao: FundPersonDao,
    private val personsDao: PersonsDao,
    private val companyDao: CompanyDao,
    private val fundBusinessDataDao: FundBusinessDataDao,
    private val fundNavDataDao: FundNavDataDao,
    private val fundReturnDataDao: FundReturnDataDao,
    private val investmentsDao: InvestmentsDao,
    private val personInvestmentExperienceDao: PersonInvestmentExperienceDao,
    private val contrastDao: ContrastDao,
    private val contrastBusinessDataDao: ContrastBusinessDataDao,
    private val fundService: FundService,
    private val contrastService: ContrastService,
    private val contrastNavDataDao: ContrastNavDataDao,
    private val companyController: CompanyController,
    private val fundDepthResearchDao: FundDepthResearchDao,
    private val fundDepthResearchService: FundDeepResearchService
) {

    companion object {

        private val log: Logger = LoggerFactory.getLogger(FundController::class.java)

        val importingDataFundIds = ConcurrentHashSet<Int>()

        private val monthShortName: Map<String, String> = mapOf(
            Pair("JANUARY", "JAN"),
            Pair("FEBRUARY", "FEB"),
            Pair("MARCH", "MAR"),
            Pair("APRIL", "APR"),
            Pair("MAY", "MAY"),
            Pair("JUNE", "JUNE"),
            Pair("JULY", "JULY"),
            Pair("AUGUST", "AUG"),
            Pair("SEPTEMBER", "SEPT"),
            Pair("OCTOBER", "OCT"),
            Pair("NOVEMBER", "NOV"),
            Pair("DECEMBER", "DEC"),
        )
    }

    @PostMapping("/baseWithTranche")
    @Operation(description = "新增/编辑基金基础信息-有份额版本")
    @Transactional(rollbackOn = [Exception::class])
    open fun saveOrUpdateFundWithTranche(
        @RequestBody @Validated fundSaveOrUpdateVo: FundSaveOrUpdateVo,
        deleteOldTranches: Boolean? = true
    ): CommonResult<String> {
        if (fundSaveOrUpdateVo.companyInfo != null) {
            val companyInfo = fundSaveOrUpdateVo.companyInfo!!
            if (StringUtils.hasText(companyInfo.companyName)) {
                var company = companyDao.findCompanyByCompanyNameEquals(companyInfo.companyName!!)
                if (company == null) {
                    company = companyInfo
                } else {
                    BeanUtils.copyProperties(companyInfo, company, "companyId")
                }
                companyController.saveOrUpdateCompany(company)
                fundSaveOrUpdateVo.fund.companyId = company.companyId!!
                fundSaveOrUpdateVo.fund.companyName = company.companyName!!
                fundSaveOrUpdateVo.fund.companyAddress = company.companyAddress
            }
        }
        saveOrUpdateFund(fundSaveOrUpdateVo.fund)
        saveOrUpdateFundTranche(
            fundSaveOrUpdateVo.fund.fundId!!,
            fundSaveOrUpdateVo.fundTranches,
            deleteOldTranches ?: true
        )
        return CommonResult.success(ResultCode.SUCCESS.message)
    }


    @PostMapping("/base")
    @Operation(description = "新增/编辑基金基础信息")
    @Transactional(rollbackOn = [Exception::class])
    open fun saveOrUpdateFund(@Validated @RequestBody fund: Fund): CommonResult<String> {
        if (fund.companyId != null || StringUtils.hasText(fund.companyName)) {
            var company = if (fund.companyId == null) {
                companyDao.findCompanyByCompanyNameEquals(fund.companyName!!)
            } else {
                companyDao.findById(fund.companyId!!).orElseThrow { SystemException("成员不存在") }
            }
            if (company == null) {
                company = Company()
                company.companyName = fund.companyName
                companyDao.save(company)
                fund.companyId = company.companyId
            }
            fund.companyId = company.companyId
            fund.companyAddress = company.companyAddress
            fund.companyName = company.companyAddress
        }
        fund.updateTime = LocalDateTime.now()
        fundService.buildFundCode(fund).let { newFundCode ->
            if (fund.fundCode != null && fund.fundCode != newFundCode) {
                fundService.deleteFundCode(fund)
            }
            fund.fundCode = newFundCode
        }
        if (!StringUtils.hasText(fund.fundShortName)) {
            fund.fundShortName = fund.fundName
        }
        fundDao.save(fund)
        fundBusinessDataDao.updateFundNameByFundId(fund.fundId!!, fund.fundName!!)
        fundNavDataDao.updateFundNameByFundId(fund.fundId!!, fund.fundName!!)
        fundReturnDataDao.updateFundNameByFundId(fund.fundId!!, fund.fundName!!)
        fundDepthResearchService.handleDepthResearchInfo(fund, FundDepthResearch(fund.fundId!!))
        return CommonResult.success(ResultCode.SUCCESS.message)
    }

    @GetMapping("/base")
    @Operation(description = "查询基金基础信息")
    fun getFundBaseInfo(@RequestParam fundId: Int): CommonResult<Fund> {
        return fundDao.findById(fundId)
            .map {
//                val (yearNav, yearStandardDeviation) = fundYearInfo(it.fundId!!)
//                it.yearNav = yearNav
//                it.yearStandardDeviation = yearStandardDeviation
                CommonResult.success(it)
            }
            .orElse(CommonResult.failed())
    }

    @DeleteMapping
    @Operation(description = "删除基金")
    @Transactional(rollbackOn = [Exception::class])
    open fun deleteFund(@RequestParam fundId: Int): CommonResult<String?> {
        if (importingDataFundIds.contains(fundId)) {
            return CommonResult.failed("数据后台处理中,请稍后再操作")
        }
        val fundOptional = fundDao.findById(fundId)
        if (fundOptional.isEmpty) {
            return CommonResult.failed("基金信息不存在")
        }
        val fund = fundOptional.get()
        fundDao.deleteById(fundId)
        fundTrancheDao.deleteAllByFundId(fundId)
        fundInternalScoreDao.deleteAllByFundId(fundId)
        fundInvestmentsDao.deleteAllByFundId(fundId)
        fundPersonDao.deleteAllByFundId(fundId)
        fundBusinessDataDao.deleteAllByFundId(fundId)
        fundNavDataDao.deleteAllByFundId(fundId)
        fundReturnDataDao.deleteAllByFundId(fundId)
        fundDepthResearchDao.deleteAllByFundIdEquals(fundId)
        fundService.deleteFundCode(fund)
        return CommonResult.success(ResultCode.SUCCESS.message)
    }

    @GetMapping("/importFundTemplate")
    @Operation(description = "基金导入模板下载")
    open fun getBaseImportTemplate(response: HttpServletResponse) {
        response.contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE
        response.characterEncoding = StandardCharsets.UTF_8.name()
        response.setHeader(
            HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(
                "基金导入.xlsx", StandardCharsets.UTF_8
            )
        )
        response.outputStream.use { fos ->
            EasyExcel.write(fos, Fund::class.java)
                .excelType(ExcelTypeEnum.XLSX)
                .sheet("模板")
                .doWrite {
                    emptyList<Fund>()
                }
        }
    }

    @PostMapping("/importFundTemplate")
    @Operation(description = "基金模板导入")
    @Transactional(rollbackOn = [Exception::class])
    open fun importBaseTemplate(
        @RequestParam("excelFile") excelFile: MultipartFile
    ): CommonResult<String> {
        excelFile.inputStream.use { fis ->
            EasyExcel.read(fis)
                .head(Fund::class.java)
                .doReadAllSync<Fund>()
                .forEach { fund ->
                    if (!StringUtils.hasText(fund.fundName)) {
                        return@forEach
                    }
                    val dbFund = fundDao.findByFundName(fund.fundName!!)
                    if (dbFund != null) {
                        return@forEach
                    }
                    companyDao.findCompanyByCompanyNameEquals(fund.companyName!!)?.let { dbCompany ->
                        fund.companyId = dbCompany.companyId
                        fund.companyAddress = dbCompany.companyAddress
                    } ?: run {
                        val company = Company()
                        company.companyName = fund.companyName
                        companyDao.save(company)
                        fund.companyId = company.companyId
                    }
                    //导入时，默认简称为基金名称
                    fund.fundShortName = fund.fundName
                    fund.fundCode = fundService.buildFundCode(fund)
                    fundDao.save(fund)
                }
        }
        return CommonResult.success(ResultCode.SUCCESS.message);
    }

    @GetMapping("/tranche")
    @Operation(description = "查询基金对应的所有的基金份额")
    fun getFundTranche(@RequestParam fundId: Int): CommonResult<Collection<FundTranche>> {
        return CommonResult.success(fundTrancheDao.findAllByFundId(fundId))
    }

    @PostMapping("/tranche")
    @Operation(description = "修改基金对应的所有的基金份额,有id的修改,没有的新增")
    @Transactional(rollbackOn = [Exception::class])
    open fun saveOrUpdateFundTranche(
        @RequestParam fundId: Int,
        @RequestBody fundTranches: Collection<FundTranche>,
        deleteOldTranches: Boolean? = true
    ): CommonResult<String> {
        val fund = fundDao.findByIdOrNull(fundId) ?: return CommonResult.success(ResultCode.NO_DATA.message)
        if (fundTranches.isEmpty() && (deleteOldTranches ?: true)) {
            fundTrancheDao.deleteAllByFundId(fundId)
            return CommonResult.success(ResultCode.SUCCESS.message)
        }
        fundTrancheDao.deleteAllByFundId(fundId)
        var baseClassed = false
        for (fundTranche in fundTranches) {
            if (fundTranche.id == null) {
                fundTranche.insertTime = LocalDateTime.now()
            } else {
                fundTranche.updateTime = LocalDateTime.now()
            }
            fundTranche.id = null
            fundTranche.fundId = fund.fundId
            fundTranche.baseClass = (if (baseClassed) false else fundTranche.baseClass ?: false)
            if (!baseClassed && (fundTranche.baseClass!!)) {
                baseClassed = true
            }
            fundTrancheDao.save(fundTranche)
        }
        return CommonResult.success(ResultCode.SUCCESS.message)
    }

    @GetMapping("/importTrancheTemplate")
    @Operation(description = "基金份额导入模板下载")
    fun getTrancheImportTemplate(response: HttpServletResponse) {
        response.contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE
        response.characterEncoding = StandardCharsets.UTF_8.name()
        response.setHeader(
            HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(
                "基金份额导入.xlsx", StandardCharsets.UTF_8
            )
        )
        response.outputStream.use { fos ->
            EasyExcel.write(fos, FundTranche::class.java)
                .excelType(ExcelTypeEnum.XLSX)
                .sheet("模板")
                .doWrite {
                    emptyList<FundTranche>()
                }
        }
    }

    @PostMapping("/importTrancheTemplate")
    @Operation(description = "基金份额模板导入")
    @Transactional(rollbackOn = [Exception::class])
    open fun importTrancheTemplate(
        @RequestParam fundId: Int,
        @RequestParam("excelFile") excelFile: MultipartFile
    ): CommonResult<String> {
        excelFile.inputStream.use { fis ->
            EasyExcel.read(fis)
                .head(FundTranche::class.java)
                .doReadAllSync<FundTranche>()
                .forEach { fundTranche ->
                    val dbFundTranche = fundTrancheDao.findByName(fundTranche.name!!)
                    if (dbFundTranche != null) {
                        BeanUtils.copyProperties(fundTranche, dbFundTranche, "name", "id", "insertTime")
                        dbFundTranche.updateTime = LocalDateTime.now()
                        fundTrancheDao.save(dbFundTranche)
                    } else {
                        fundTranche.insertTime = LocalDateTime.now()
                        fundTranche.fundId = fundId
                        fundTrancheDao.save(fundTranche)
                    }
                }
        }
        return CommonResult.success(ResultCode.SUCCESS.message);
    }

    @PostMapping("/score")
    @Operation(description = "新增/修改打分信息")
    fun saveOrUpdateFundInternalScore(
        @RequestParam fundId: Int,
        @RequestBody fundInternalScore: FundInternalScore
    ): CommonResult<String> {
        fundInternalScore.fundId = fundId
        fundInternalScoreDao.save(fundInternalScore)
        return CommonResult.success(ResultCode.SUCCESS.message)
    }

    @GetMapping("/score")
    @Operation(description = "查询基金打分信息")
    fun getFundFundInternalScore(@RequestParam fundId: Int): CommonResult<FundInternalScore> {
        return CommonResult.success(
            fundInternalScoreDao.findByFundId(fundId)
                .map { it ->
                    it.fundName = fundDao.findFundNameById(it.fundId!!) ?: ""
                    it
                }
                .orElse(FundInternalScore.empty(fundId))
        )
    }

    @GetMapping("/score/history")
    @Operation(description = "查询基金历史打分信息")
    fun getFundInternalScoreHistory(@RequestParam fundId: Int): CommonResult<List<FundInternalScoreHistory>> {
        val fund = fundDao.findById(fundId).orElseThrow { SystemException("指定基金不存在") }
        return CommonResult.success(
            fundInternalScoreHistoryDao.findAllByFundIdEqualsOrderByCreateTimeDesc(fundId)
                .onEach { it.fundName = fund.fundName!! }
        )
    }

    @PostMapping("/scores")
    @Operation(description = "批量查询基金打分信息")
    fun getFundFundInternalScores(@RequestBody fundIds: List<Int>): CommonResult<List<FundInternalScore>> {
        return CommonResult.success(
            fundInternalScoreDao.findAllByFundIdIn(fundIds).onEachAsyncBlocking { fundScore ->
                fundScore.fundName = fundDao.findFundNameById(fundScore.fundId!!) ?: ""
            }
        )
    }

    @GetMapping("/investments")
    @Operation(description = "查询基金对应的所有的投资案例")
    fun getFundInvestments(@RequestParam fundId: Int): CommonResult<Collection<FundInvestments>> {
        return CommonResult.success(fundInvestmentsDao.findAllByFundId(fundId))
    }

    @GetMapping("/investmentsByName")
    @Operation(description = "根据名字查询基金对应的所有的投资案例")
    fun getFundInvestmentsByName(@RequestParam fundName: String): CommonResult<Collection<FundInvestments>> {
        val fund = fundDao.findByFundName(fundName) ?: throw SystemException("基金未找到")
        return CommonResult.success(fundInvestmentsDao.findAllByFundId(fund.fundId!!))
    }

    @PostMapping("/investments")
    @Operation(description = "修改基金的投资案例,有id的修改,没有的新增")
    @Transactional(rollbackOn = [Exception::class])
    open fun saveOrUpdateFundInvestments(
        @RequestParam fundId: Int,
        @RequestBody fundInvestments: FundInvestments
    ): CommonResult<String> {
        if (fundInvestments.id != null) {
            fundInvestmentsDao.findById(fundInvestments.id!!)
                .ifPresent {
                    it.modified = true
                    fundInvestmentsDao.save(it)
                }
        }
        fundInvestments.id = null
        fundInvestments.fundId = fundId
        fundInvestments.changeScale = BigDecimal.ZERO
        fundInvestments.modified = false
        var investments = investmentsDao.findByName(fundInvestments.investmentName!!)
        if (investments == null) {
            investments = Investments()
            investments.name = fundInvestments.investmentName!!
            investments.aum = BigDecimal.ZERO
            investmentsDao.save(investments)
        }
        fundInvestments.investmentId = investments.id
        fundInvestments.amount = (fundInvestments.scale?.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
            ?.multiply(investments.aum ?: BigDecimal.ZERO)) ?: BigDecimal.ZERO
        fundInvestments.cleanDays =
            if ((investments.mobility ?: BigDecimal.ZERO)?.compareTo(BigDecimal.ZERO) == 0) {
                BigDecimal.ZERO
            } else {
                fundInvestments.amount!!.divide(
                    investments.mobility!!.multiply(BigDecimal.TEN),
                    0, RoundingMode.UP
                )
            }
        fundInvestmentsDao.save(fundInvestments)
        return CommonResult.success(ResultCode.SUCCESS.message)
    }

    @DeleteMapping("/investments")
    @Operation(description = "删除基金的投资案例")
    @Transactional(rollbackOn = [Exception::class])
    open fun deleteFundInvestments(
        @RequestParam fundInvestmentsId: Int,
    ): CommonResult<String> {
        fundInvestmentsDao.deleteById(fundInvestmentsId)
        return CommonResult.success(ResultCode.SUCCESS.message)
    }


    @GetMapping("/persons")
    @Operation(description = "查询基金对应的所有的成员")
    fun getFundPerson(@RequestParam fundId: Int): CommonResult<FundPersonVo> {
        val totalFundPersons = fundPersonDao.findAllByFundId(fundId)
        return runBlocking(Dispatchers.Default) {
            val currentPersons =
                totalFundPersons.filter { it.endDate == null || it.endDate!!.isAfter(LocalDate.now()) }
                    .mapAsync { fundPerson ->
                        val persons =
                            personsDao.findById(fundPerson.personId!!).orElseThrow { SystemException("用户不存在") }
                        fundPerson.linkedin = persons.linkedin
                        fundPerson.note = persons.note
                        fundPerson.currentCompanyPosition = persons.currentCompanyPosition
                        fundPerson
                    }
            val historyPersons =
                totalFundPersons.filter { it.endDate != null && it.endDate!!.isBefore(LocalDate.now()) }
                    .mapAsync { fundPerson ->
                        val persons =
                            personsDao.findById(fundPerson.personId!!).orElseThrow { SystemException("用户不存在") }
                        fundPerson.linkedin = persons.linkedin
                        fundPerson.note = persons.note
                        fundPerson.currentCompanyPosition = persons.currentCompanyPosition
                        fundPerson
                    }
            return@runBlocking CommonResult.success(FundPersonVo(currentPersons.awaitAll(), historyPersons.awaitAll()))
        }
    }

    @PostMapping("/persons")
    @Operation(description = "修改基金对应的所有的成员,有id的修改,没有的新增")
    @Transactional(rollbackOn = [Exception::class])
    open fun saveOrUpdateFundPersons(
        @RequestParam fundId: Int,
        @RequestBody fundPersonVo: FundPersonVo
    ): CommonResult<String> {
        val fundPersonIds = mutableSetOf<Int>()
        fundPersonVo.currentPersons?.forEach { fundPerson ->
            fundPerson.fundId = fundId
            val person = if (fundPerson.personId == null) {
                val newPerson = Persons()
                newPerson.createTime = LocalDateTime.now()
                newPerson.name = fundPerson.name
                personsDao.save(newPerson)
                fundPerson.personId = newPerson.personId
                newPerson
            } else {
                personsDao.findById(fundPerson.personId!!).orElseThrow { SystemException("成员不存在") }
            }
            if (fundPerson.corePosition == null) {
                fundPerson.corePosition = false
            }
            fundPerson.personId = person.personId
            fundPerson.name = person.name
            fundPerson.englishName = person.englishName
            fundPersonDao.save(fundPerson)

            fundPersonIds.add(fundPerson.id!!)
        }
        fundPersonVo.historyPersons?.forEach { fundPerson ->
            fundPerson.fundId = fundId
            val person = if (fundPerson.personId == null) {
                val newPerson = Persons()
                newPerson.createTime = LocalDateTime.now()
                newPerson.name = fundPerson.name
                personsDao.save(newPerson)
                fundPerson.personId = newPerson.personId
                newPerson
            } else {
                personsDao.findById(fundPerson.personId!!).orElseThrow { SystemException("成员不存在") }
            }
            if (fundPerson.corePosition == null) {
                fundPerson.corePosition = false
            }
            fundPerson.personId = person.personId
            fundPerson.name = person.name
            fundPerson.englishName = person.englishName
            fundPersonDao.save(fundPerson)
            fundPersonIds.add(fundPerson.id!!)
        }
        if (fundPersonIds.isNotEmpty()) {
            fundPersonDao.deleteAllByFundIdEqualsAndIdNotIn(fundId, fundPersonIds)
        } else {
            fundPersonDao.deleteAllByFundId(fundId)
        }
        return CommonResult.success(ResultCode.SUCCESS.message)
    }

    @Operation(description = "基金列表")
    @PostMapping(value = ["/list"])
    fun getFundList(@Validated @RequestBody fundPageParam: FundPageParam): CommonResult<CommonPage<FundPageVo>> {
        val page = PageUtil.buildJpaPage<FundPageVo>(fundPageParam.pageNum, fundPageParam.pageSize)
        val count = fundDao.countFund(fundPageParam)
        if (count > 0) {
            val fundPageVos = fundDao.findFund(fundPageParam)
                .mapAsyncBlocking { fund ->
                    val fundPageVo = FundPageVo()
                    BeanUtils.copyProperties(fund, fundPageVo)
                    val fundId = fund.fundId!!
                    fundPageVo.fundCorePersons =
                        fundPersonDao.findAllByFundId(fundId).filter { it.corePosition == true }
                            .map { FundCorePerson(it.personId!!, it.name!!) }
                    fund.companyId?.let { companyId ->
                        companyDao.findById(companyId).ifPresent { company ->
                            fundPageVo.companyCity = company.companyCity
                        }
                    }
                    val currentYear = LocalDate.now().year
                    val lastMonth = LocalDate.now().minusMonths(1).atEndOfMonth()
                    fundPageVo.currentMonthRequite =
                        fundBusinessDataDao.findFundBusinessDataByFundIdAndTime(fundId, lastMonth)?.value
                    fundPageVo.currentYearNav = fundBusinessDataDao.findAllByFundIdAndTimeBetween(
                        fundId,
                        LocalDate.of(currentYear, 1, 1),
                        LocalDate.now(),
                    ).map { it.value }.ytd(true)
                    val fundDepthResearch = fundDepthResearchDao.findByFundIdEquals(fundId)
                    fundPageVo.screenOverallRate =
                        fundDepthResearch?.screenOverallRate ?: BigDecimal.ZERO
                    fundPageVo.meetingRate =
                        fundDepthResearch?.meetingRate ?: BigDecimal.ZERO
                    fundPageVo.investmentRate =
                        fundDepthResearch?.investmentRate ?: BigDecimal.ZERO
                    fundPageVo.riskRate =
                        fundDepthResearch?.riskRate ?: BigDecimal.ZERO
                    fundPageVo.keyManRate =
                        fundDepthResearch?.keyManRate ?: BigDecimal.ZERO
                    fundPageVo.businessRate =
                        fundDepthResearch?.businessRate ?: BigDecimal.ZERO
                    fundPageVo
                }
            val commonPage = CommonPage<FundPageVo>()
            commonPage.pageNum = page.pageNumber.toLong()
            commonPage.pageSize = page.pageSize.toLong()
            commonPage.total = count
            commonPage.totalPage = count / page.pageSize + 1
            commonPage.list = fundPageVos
            return CommonResult.success(commonPage)
        }
        return CommonResult.success(CommonPage.empty(page.pageNumber.toLong(), page.pageSize.toLong()))
    }


    @Operation(description = "月度表现")
    @PostMapping(value = ["/monthlyManifestation"])
    fun monthlyManifestation(@RequestBody batchFundDataQueryVo: BatchFundDataQueryVo): CommonResult<Collection<FundYtdValueVo>?> {
        // 此接口中的 fundIds 不会使用
        val baseFund =
            fundDao.findById(batchFundDataQueryVo.baseFundId).orElseThrow { SystemException("基准基金信息不存在") }
        val (fundValueMinDate, fundValueMaxDate) = fundService.fundDataTimeRange(batchFundDataQueryVo.baseFundId)

        val fundYtdValues = fundBusinessDataDao.findAllByFundIdAndTimeBetween(
            batchFundDataQueryVo.baseFundId,
            fundValueMinDate,
            fundValueMaxDate
        ).groupBy {
            it.time!!.year
        }.mapAsyncBlocking { entry ->
            val year = entry.key
            val yearBusinessData = entry.value
            val sortedFundBusinessDatas = yearBusinessData.sortedBy { it.time!! }
            val yearMonthlyManifestation = YearMonthlyManifestation()
            yearMonthlyManifestation.year = year
            yearMonthlyManifestation.monthlyManifestation =
                sortedFundBusinessDatas.map { MonthValueVo(monthShortName[it.time!!.month.name]!!, it.value) }
            // 2025-04-27 修改为 ytd = (1+m1)(1+m2)...(1+m12) - 1 m为月份
            // 2025-06-05 如果年份未当前实际的年,就不计算ytd
//            yearMonthlyManifestation.ytd = if (year == LocalDate.now().year) {
//                null
//            } else {
//                sortedFundBusinessDatas.map {
//                    it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP)
//                }.ytd()
//            }
            // 2025年7月27日 修改为当前实际的年计算 ytd
            yearMonthlyManifestation.ytd = sortedFundBusinessDatas.map {
                it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP)
            }.ytd()

            yearMonthlyManifestation.contrastYtds = batchFundDataQueryVo.contrastIds.mapAsyncBlocking { contrastId ->
                val contrast = contrastDao.findById(contrastId).orElse(null) ?: return@mapAsyncBlocking null
                val startDate = if (fundValueMinDate.year == year) {
                    fundValueMinDate
                } else {
                    LocalDate.of(year, 1, 1)
                }
                val endDate = if (fundValueMaxDate.year == year) {
                    fundValueMaxDate
                } else {
                    LocalDate.of(year, 12, 31)
                }
                val ytd = contrastBusinessDataDao.findAllByContrastIdAndTimeBetween(contrastId, startDate, endDate)
                    .map {
                        it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP)
                    }.ytd()
                MonthValueVo(contrast.name, ytd)
            }.filterNotNull()
            yearMonthlyManifestation
        }



        return CommonResult.success(
            listOf(
                FundYtdValueVo(
                    baseFund.fundId!!,
                    baseFund.fundName!!,
                    fundYtdValues
                )
            )
        )
    }


    @GetMapping("/relation")
    @Operation(description = "基金关系")
    fun fundRelation(@RequestParam fundId: Int): CommonResult<FundRelationVo> {
        val fund = fundDao.findById(fundId).orElseThrow { SystemException("基金不存在") }
        return runBlocking(Dispatchers.Default) {
            val fundInvestmentRelations =
                // 先根据基金 id 基金参与过的投资案例信息
                fundInvestmentsDao.findAllByFundId(fundId).distinctBy { it.investmentId!! }.mapAsync { fundInvestment ->
                    //  再根据投资案例 id 找到所有参与过该投资案例的基金信息
                    val investmentsFundRelations =
                        fundInvestmentsDao.findAllByInvestmentId(fundInvestment.investmentId!!)
                            .map { it.fundId!! }
                            .filter { it != fundId }
                            .distinct()
                            .map { subInvestmentFundId ->
                                FundRelationNode(
                                    subInvestmentFundId,
                                    fundDao.findById(subInvestmentFundId)
                                        .orElseThrow { SystemException("基金不存在") }.fundName!!,
                                    RelationType.FUND,
                                    emptyList()
                                )
                            }
                    FundInvestmentsRelationVo(
                        fundInvestment.investmentId!!, fundInvestment.investmentName!!,
                        fundInvestment.reason, fundInvestment.scale,
                        fundInvestment.updateTime, RelationType.INVESTMENT,
                        investmentsFundRelations
                    )
                }
            val fundCompanyRelations = async {
                fund.companyId?.let { companyId ->
                    val company = companyDao.findById(companyId).orElseThrow { SystemException("公司不存在") }
                    listOf(
                        FundCompanyRelationVo(
                            companyId, company.companyName!!, RelationType.COMPANY,
                            fundDao.findAllByCompanyId(companyId).filter { it.fundId!! != fundId }.map { fund ->
                                FundRelationNode(fund.fundId!!, fund.fundName!!, RelationType.FUND, emptyList())
                            })
                    )
                } ?: emptyList()
            }
            val fundPersonRelations =
                // 先找到基金的人员
                fundPersonDao.findAllByFundId(fundId).mapAsync { fundPerson ->
                    // 再找到人员参与的投资案例
                    val personAllInvestments =
                        personInvestmentExperienceDao.findAllByPersonIdEquals(fundPerson.personId!!)
                    val personInvestmentRelations = personAllInvestments.map { personInvestmentExperience ->
                        val investments = investmentsDao.findById(personInvestmentExperience.investmentId!!)
                            .orElseThrow { SystemException("投资案例不存在") }
                        // 再根据投资案例找参与过该投资的基金信息
                        val investmentFundRelations =
                            fundInvestmentsDao.findAllByInvestmentId(personInvestmentExperience.investmentId!!)
                                .distinctBy { it.fundId!! }
                                .filter { it.fundId!! != fundId }
                                .map { fundInvestment ->
                                    val currentFund =
                                        fundDao.findById(fundInvestment.fundId!!)
                                            .orElseThrow { SystemException("基金不存在") }
                                    FundRelationNode(
                                        fundInvestment.fundId!!,
                                        currentFund.fundName!!,
                                        RelationType.FUND,
                                        emptyList()
                                    )
                                }
                        FundPersonInvestmentsRelationVo(
                            investments.id!!,
                            investments.name!!,
                            investments.aum,
                            personInvestmentExperience.scale,
                            personInvestmentExperience.amount,
                            personInvestmentExperience.startInvestTime,
                            personInvestmentExperience.updateTime,
                            RelationType.INVESTMENT,
                            investmentFundRelations
                        )
                    }

                    FundPersonRelationVo(
                        fundPerson.personId!!,
                        fundPerson.name!!,
                        fundPerson.position,
                        fundPerson.startDate,
                        fundPerson.endDate,
                        fundPerson.note,
                        RelationType.PERSON,
                        fundPerson.corePosition ?: false,
                        personInvestmentRelations
                    )
                }
            return@runBlocking CommonResult.success(
                FundRelationVo(
                    fundId, fund.fundName!!,
                    fundInvestmentRelations.awaitAll(), fundPersonRelations.awaitAll(), fundCompanyRelations.await()
                )
            )
        }
    }

    @GetMapping("/importFundBusinessDataNew")
    @Operation(description = "基金数据模板下载-新模板")
    fun importFundBusinessDataTemplateNew(response: HttpServletResponse) {
        response.contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE
        response.characterEncoding = StandardCharsets.UTF_8.name()
        response.setHeader(
            HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(
                "基金数据导入模板.xlsx", StandardCharsets.UTF_8
            )
        )
        response.outputStream.use { fos ->
            ClassPathResource("static/基金数据导入模板.xlsx").inputStream.use { fis ->
                fis.transferTo(fos)
            }
        }
    }

    @PostMapping("/importFundBusinessDataNew")
    @Operation(description = "基金数据导入-新模板")
    fun importFundBusinessDataNew(
        @RequestParam("excelFile") excelFile: MultipartFile,
        @RequestParam("code") code: String
    ): CommonResult<String> {
        val tempExcelFile =
            Paths.get(System.getProperty("user.dir") + "/temp/" + UUID.randomUUID().toString() + ".xlsx")
        Files.newOutputStream(tempExcelFile)
            .use { fos ->
                excelFile.inputStream.use { fis -> fis.transferTo(fos) }
            }
        Thread.startVirtualThread {
            val time = measureTime {
                loadFundBusinessDataFromFileNew(tempExcelFile, code)
            }
            log.info("导入文件[{}]耗时[{}]", tempExcelFile.toString(), time)
            Files.deleteIfExists(tempExcelFile)
        }
        return CommonResult.success(ResultCode.SUCCESS.message)
    }


    @GetMapping("/contrastNames")
    @Operation(description = "指数名称列表")
    fun allContrastNames(): CommonResult<List<Contrast>> {
        return CommonResult.success(contrastDao.findAll())
    }

    @Operation(description = "月度业绩")
    @PostMapping("/monthAnnualizedResults")
    fun fundMonthAnnualizedResults(@RequestBody batchFundDataQueryVo: BatchFundDataQueryVo): CommonResult<List<FundMonthBusinessDataVo>> {
        val baseFund =
            fundDao.findById(batchFundDataQueryVo.baseFundId).orElseThrow { SystemException("基准基金信息不存在") }
        val (fundValueMinDate, fundValueMaxDate) = fundService.fundDataTimeRange(batchFundDataQueryVo.baseFundId)
        val baseFundMonthlyManifestation = fundBusinessDataDao.findAllByFundIdAndTimeBetween(
            batchFundDataQueryVo.baseFundId,
            fundValueMinDate,
            fundValueMaxDate
        ).map { MonthValueVo(monthShortName[it.time!!.month.name]!!, it.value) }
        val baseFundMonthBusinessData =
            FundMonthBusinessDataVo(baseFund.fundId!!, baseFund.fundName!!, baseFundMonthlyManifestation, 1)

        val fundInfos = batchFundDataQueryVo.fundIds.mapAsyncBlocking { fundId ->
            val fund = fundDao.findByIdOrNull(fundId) ?: return@mapAsyncBlocking null
            val monthlyManifestation = fundBusinessDataDao.findAllByFundIdAndTimeBetween(
                fundId,
                fundValueMinDate,
                fundValueMaxDate
            ).map { MonthValueVo(monthShortName[it.time!!.month.name]!!, it.value) }
            FundMonthBusinessDataVo(fundId, fund.fundName!!, monthlyManifestation, 1)
        }
        val contrastInfos = batchFundDataQueryVo.contrastIds.mapAsyncBlocking { contrastId ->
            val contrast = contrastDao.findByIdOrNull(contrastId) ?: return@mapAsyncBlocking null
            val monthlyManifestation = contrastBusinessDataDao.findAllByContrastIdAndTimeBetween(
                contrastId,
                fundValueMinDate,
                fundValueMaxDate
            ).map { MonthValueVo(monthShortName[it.time!!.month.name]!!, it.value) }
            FundMonthBusinessDataVo(contrastId, contrast.name, monthlyManifestation, 2)
        }
        return CommonResult.success(
            listOf(
                listOf(baseFundMonthBusinessData),
                fundInfos.filterNotNull(),
                contrastInfos.filterNotNull()
            ).flatten()
        )
    }


    @PostMapping("/yearAnnualizedResults")
    @Operation(description = "基金年化业绩")
    fun fundYearAnnualizedResults(@RequestBody batchFundDataQueryVo: BatchFundDataQueryVo): CommonResult<List<YearAnnualizedResults>> =
        runBlocking {
            val baseFund =
                fundDao.findById(batchFundDataQueryVo.baseFundId).orElseThrow { SystemException("基准基金信息不存在") }
            val (fundValueMinDate, fundValueMaxDate) = fundService.fundDataTimeRange(batchFundDataQueryVo.baseFundId)
            val baseFundInfo = async {
                fundService.fundYearAnnualizedResults(
                    batchFundDataQueryVo.baseFundId,
                    fundValueMinDate,
                    fundValueMaxDate
                )
            }
            val fundData = batchFundDataQueryVo.fundIds.mapAsync { fundId ->
                fundService.fundYearAnnualizedResults(fundId, fundValueMinDate, fundValueMaxDate)
            }
            val contrastData = batchFundDataQueryVo.contrastIds.mapAsync { contrastId ->
                contrastService.contrastYearAnnualizedResults(contrastId, fundValueMinDate, fundValueMaxDate)
            }
            return@runBlocking CommonResult.success(
                listOf(
                    listOf(baseFundInfo.await()),
                    fundData.awaitAll(),
                    contrastData.awaitAll()
                ).flatten()
            )
        }

    @PostMapping("/yearAnnualResults")
    @Operation(description = "基金年度业绩")
    fun fundYearAnnualResults(@RequestBody batchFundDataQueryVo: BatchFundDataQueryVo): CommonResult<List<YearAnnualizedResults>> =
        runBlocking {
            val baseFund =
                fundDao.findById(batchFundDataQueryVo.baseFundId).orElseThrow { SystemException("基准基金信息不存在") }
            val (fundValueMinDate, fundValueMaxDate) = fundService.fundDataTimeRange(batchFundDataQueryVo.baseFundId)
            val baseFundInfo = async {
                fundService.fundYearAnnualResults(batchFundDataQueryVo.baseFundId, fundValueMinDate, fundValueMaxDate)
            }
            val fundData = batchFundDataQueryVo.fundIds.mapAsync { fundId ->
                fundService.fundYearAnnualResults(fundId, fundValueMinDate, fundValueMaxDate)
            }
            val contrastData = batchFundDataQueryVo.contrastIds.mapAsync { contrastId ->
                contrastService.contrastYearAnnualResults(contrastId, fundValueMinDate, fundValueMaxDate)
            }
            return@runBlocking CommonResult.success(
                listOf(
                    listOf(baseFundInfo.await()),
                    fundData.awaitAll(),
                    contrastData.awaitAll()
                ).flatten()
            )
        }

    @PostMapping("/quarterAnnualizedResults")
    @Operation(description = "基金季度业绩")
    fun fundQuarterAnnualizedResults(@RequestBody batchFundDataQueryVo: BatchFundDataQueryVo): CommonResult<List<QuarterAnnualizedResults>> =
        runBlocking {
            val baseFund =
                fundDao.findById(batchFundDataQueryVo.baseFundId).orElseThrow { SystemException("基准基金信息不存在") }
            val (fundValueMinDate, fundValueMaxDate) = fundService.fundDataTimeRange(batchFundDataQueryVo.baseFundId)
            val baseFundInfo = async {
                fundService.fundQuarterAnnualizedResults(
                    batchFundDataQueryVo.baseFundId,
                    fundValueMinDate,
                    fundValueMaxDate
                )
            }
            val fundData = batchFundDataQueryVo.fundIds.mapAsync { fundId ->
                fundService.fundQuarterAnnualizedResults(fundId, fundValueMinDate, fundValueMaxDate)
            }
            val contrastData = batchFundDataQueryVo.contrastIds.mapAsync { contrastId ->
                contrastService.contrastQuarterAnnualizedResults(contrastId, fundValueMinDate, fundValueMaxDate)
            }
            return@runBlocking CommonResult.success(
                listOf(
                    listOf(baseFundInfo.await()),
                    fundData.awaitAll(),
                    contrastData.awaitAll()
                ).flatten()
            )
        }


    private fun loadFundBusinessDataFromFileNew(
        excelFile: Path,
        code: String,
    ) {
        val allFundIds = mutableSetOf<Int>()
        val currentPercent = LongAdder()
        Files.newInputStream(excelFile).use { fis ->
            val workBook = XSSFWorkbook(fis)
            workBook.getSheet("基金")?.let { fundSheet ->
                log.info("开始导入基金")
                allFundIds.addAll(readFundData(fundSheet))
                currentPercent.add(20)
                FundImportStatusHandler.sendInfoMessage(code, currentPercent.sum())
            }
            importingDataFundIds.addAll(allFundIds)
            workBook.getSheet("指数")?.let { contrastSheet ->
                log.info("开始导入指数")
                readContrastData(contrastSheet) > 0
                currentPercent.add(20)
                FundImportStatusHandler.sendInfoMessage(code, currentPercent.sum())
            }
            workBook.getSheet("分数")?.let { scoreSheet ->
                log.info("开始导入分数")
                readScoreData(scoreSheet)
                currentPercent.add(20)
                FundImportStatusHandler.sendInfoMessage(code, currentPercent.sum())
            }
            currentPercent.add(40L)
            FundImportStatusHandler.sendInfoMessage(code, currentPercent.sum())
        }
        allFundIds.consumeAsyncBlocking { fundId ->
            // 计算 nav
            log.info("开始计算基金[{}]的 navdata", fundId)
            fundService.calcFundNavData(fundId)
            // 计算回撤
            log.info("开始计算基金[{}]的回撤数据", fundId)
            fundService.calcFundReturnData(fundId)
            // 基金年化数据
            log.info("开始计算基金[{}]的年化数据", fundId)
            val (yearNav, yearStandardDeviation) = fundService.fundYearInfo(fundId)
            fundDao.findById(fundId).ifPresent { fund ->
                fund.yearNav = yearNav
                fund.yearStandardDeviation = yearStandardDeviation
                fundDao.save(fund)

//                log.info("开始计算基金[{}]的深度调研数据", fundId)
//                val fundDepthResearch =
//                    fundDepthResearchService.findByFundId(fundId) ?: FundDepthResearch(fundId)
//                fundDepthResearchService.handleDepthResearchInfo(fund, fundDepthResearch)
            }


        }
//        if (readContrastData) {
//            contrastDao.findAllContrastsIds().consumeAsyncBlocking { contrastId ->
        // 计算 nav
//                log.info("开始计算指数[{}]的 navdata", contrastId)
//                contrastService.calcContrastNavData(contrastId)
        // 计算回撤
//                log.info("开始计算指数[{}]的回撤数据", contrastId)
//                contrastService.calcContrastReturnDta(contrastId)
//            }
//        }
        importingDataFundIds.removeAll(allFundIds)
        log.info("基金数据文件[{}]导入完成", excelFile.fileName)
    }

    private fun readFundData(fundSheet: Sheet): Collection<Int> {
        val headRow = fundSheet.getRow(0)
        val indexFunMap = mutableMapOf<Int, Fund>()
        for (cellIndex in 1..headRow.lastCellNum) {
            val fundName = headRow.getCell(cellIndex)?.stringCellValue ?: ""
            if (!StringUtils.hasText(fundName)) {
                continue
            }
            log.info("开始读取基金[{}]的数据", fundName)
            var fund = fundDao.findByFundName(fundName)
            if (fund == null) {
                fund = Fund()
                fund.fundName = fundName
                fund.fundShortName = fundName
                fundDao.save(fund)
            }
            indexFunMap[cellIndex] = fund
        }
        for (rowIndex in 1..fundSheet.lastRowNum) {
            val dataRow = fundSheet.getRow(rowIndex)
            val timeString = dataRow.getCell(0)?.stringCellValue
            if (!StringUtils.hasText(timeString)) {
                // 空行跳过
                continue
            }
            // 统一转到到当月的最后一天
            val time = DateTimeUtils.parseDateTime(timeString!!).toLocalDate().atEndOfMonth()
            for (cellIndex in 1..dataRow.lastCellNum) {
                val fundValue = ExcelUtils.getNumericCellValueFromCell(dataRow.getCell(cellIndex))
                    ?.multiply(BigDecimal.valueOf(100))
                    ?.setScale(2, RoundingMode.HALF_UP) ?: continue
                val fund = indexFunMap[cellIndex] ?: continue
                var fundBusinessData = fundBusinessDataDao.findFundBusinessDataByFundIdAndTime(fund.fundId!!, time)
                if (fundBusinessData == null) {
                    fundBusinessData = FundBusinessData()
                    fundBusinessData.fundId = fund.fundId!!
                    fundBusinessData.fundName = fund.fundName!!
                    fundBusinessData.time = time
                }
                fundBusinessData.value = fundValue
                fundBusinessDataDao.save(fundBusinessData)
            }
        }
        return indexFunMap.values.map { it.fundId!! }
    }

    private fun readContrastData(contrastSheet: Sheet): Int {
        var dataCount = 0;
        val headRow = contrastSheet.getRow(0)
        val contrastIndexMap = mutableMapOf<Int, String>()
        for (cellIndex in 1..headRow.lastCellNum) {
            val contrastName = headRow.getCell(cellIndex)?.stringCellValue ?: ""
            if (!StringUtils.hasText(contrastName)) {
                continue
            }
            contrastIndexMap[cellIndex] = contrastName
        }
        val contrastMap = contrastDao.findAll().groupBy { it.name }.entries.associate {
            Pair(it.key, it.value[0])
        }.toMutableMap()
        for (rowIndex in 1..contrastSheet.lastRowNum) {
            val dataRow = contrastSheet.getRow(rowIndex)
            val timeString = dataRow.getCell(0)?.stringCellValue
            if (!StringUtils.hasText(timeString)) {
                return dataCount
            }
            // 统一转到到当月的最后一天
            val time = DateTimeUtils.parseDateTime(timeString!!).toLocalDate().atEndOfMonth()
            for (cellIndex in 1..dataRow.lastCellNum) {
                val contrastName = contrastIndexMap[cellIndex] ?: continue
                val contrast = if (contrastMap.containsKey(contrastName)) {
                    contrastMap[contrastName]
                } else {
                    val newContrast = contrastDao.save(Contrast(contrastName))
                    contrastMap[contrastName] = newContrast
                    newContrast
                }!!
                val value = ExcelUtils.getNumericCellValueFromCell(dataRow.getCell(cellIndex))
                    ?.multiply(BigDecimal.valueOf(100))
                    ?.setScale(2, RoundingMode.HALF_UP) ?: continue
                val contrastBusinessData: ContrastBusinessData =
                    contrastBusinessDataDao.findContrastBusinessDataByContrastIdAndTime(contrast.id!!, time)?.let {
                        it.value = value
                        it
                    } ?: ContrastBusinessData(contrast.id!!, contrastName, time, value)
                contrastBusinessDataDao.save(contrastBusinessData)
                dataCount++
            }
        }
        return dataCount
    }

    private fun readScoreData(scoreSheet: Sheet) {
        val headRow = scoreSheet.getRow(0)
        val indexFundScoreMap = mutableMapOf<Int, FundInternalScore>()
        for (cellIndex in 1..headRow.lastCellNum) {
            val fundName = headRow.getCell(cellIndex)?.stringCellValue ?: ""
            if (!StringUtils.hasText(fundName)) {
                continue
            }
            var fund = fundDao.findByFundName(fundName)
            if (fund == null) {
                fund = Fund()
                fund.fundName = fundName
                fundDao.save(fund)
            }

            indexFundScoreMap[cellIndex] =
                fundInternalScoreDao.findByFundId(fund.fundId!!).orElse(FundInternalScore.empty(fund.fundId!!))
        }
        for (rowIndex in 1..7) {
            val dataRow = scoreSheet.getRow(rowIndex)
            for (cellIndex in 1..dataRow.lastCellNum) {
                val value = dataRow.getCell(cellIndex)?.numericCellValue ?: continue
                when (rowIndex) {
                    1 -> indexFundScoreMap[cellIndex]?.earnings = value
                    2 -> indexFundScoreMap[cellIndex]?.risk = value
                    3 -> indexFundScoreMap[cellIndex]?.portfolioManagement = value
                    4 -> indexFundScoreMap[cellIndex]?.clause = value
                    5 -> indexFundScoreMap[cellIndex]?.managerMaturity = value
                    6 -> indexFundScoreMap[cellIndex]?.commercialization = value
                    7 -> indexFundScoreMap[cellIndex]?.internalResearch = value
                }
            }
        }
        fundInternalScoreDao.saveAll(indexFundScoreMap.values)
    }


}
